/*
 * Copyright (C) 2018 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3.logging

import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
import okhttp3.Call
import okhttp3.Connection
import okhttp3.EventListener
import okhttp3.Handshake
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response

/**
 * An OkHttp EventListener, which logs call events. Can be applied as an
 * [event listener factory][OkHttpClient.eventListenerFactory].
 *
 * The format of the logs created by this class should not be considered stable and may change
 * slightly between releases. If you need a stable logging format, use your own event listener.
 */
class LoggingEventListener private constructor(
  private val logger: HttpLoggingInterceptor.Logger,
) : EventListener() {
  private var startNs: Long = 0

  override fun callStart(call: Call) {
    startNs = System.nanoTime()

    logWithTime("callStart: ${call.request()}")
  }

  override fun proxySelectStart(
    call: Call,
    url: HttpUrl,
  ) {
    logWithTime("proxySelectStart: $url")
  }

  override fun proxySelectEnd(
    call: Call,
    url: HttpUrl,
    proxies: List<Proxy>,
  ) {
    logWithTime("proxySelectEnd: $proxies")
  }

  override fun dnsStart(
    call: Call,
    domainName: String,
  ) {
    logWithTime("dnsStart: $domainName")
  }

  override fun dnsEnd(
    call: Call,
    domainName: String,
    inetAddressList: List<InetAddress>,
  ) {
    logWithTime("dnsEnd: $inetAddressList")
  }

  override fun connectStart(
    call: Call,
    inetSocketAddress: InetSocketAddress,
    proxy: Proxy,
  ) {
    logWithTime("connectStart: $inetSocketAddress $proxy")
  }

  override fun secureConnectStart(call: Call) {
    logWithTime("secureConnectStart")
  }

  override fun secureConnectEnd(
    call: Call,
    handshake: Handshake?,
  ) {
    logWithTime("secureConnectEnd: $handshake")
  }

  override fun connectEnd(
    call: Call,
    inetSocketAddress: InetSocketAddress,
    proxy: Proxy,
    protocol: Protocol?,
  ) {
    logWithTime("connectEnd: $protocol")
  }

  override fun connectFailed(
    call: Call,
    inetSocketAddress: InetSocketAddress,
    proxy: Proxy,
    protocol: Protocol?,
    ioe: IOException,
  ) {
    logWithTime("connectFailed: $protocol $ioe")
  }

  override fun connectionAcquired(
    call: Call,
    connection: Connection,
  ) {
    logWithTime("connectionAcquired: $connection")
  }

  override fun connectionReleased(
    call: Call,
    connection: Connection,
  ) {
    logWithTime("connectionReleased")
  }

  override fun requestHeadersStart(call: Call) {
    logWithTime("requestHeadersStart")
  }

  override fun requestHeadersEnd(
    call: Call,
    request: Request,
  ) {
    logWithTime("requestHeadersEnd")
  }

  override fun requestBodyStart(call: Call) {
    logWithTime("requestBodyStart")
  }

  override fun requestBodyEnd(
    call: Call,
    byteCount: Long,
  ) {
    logWithTime("requestBodyEnd: byteCount=$byteCount")
  }

  override fun requestFailed(
    call: Call,
    ioe: IOException,
  ) {
    logWithTime("requestFailed: $ioe")
  }

  override fun responseHeadersStart(call: Call) {
    logWithTime("responseHeadersStart")
  }

  override fun responseHeadersEnd(
    call: Call,
    response: Response,
  ) {
    logWithTime("responseHeadersEnd: $response")
  }

  override fun responseBodyStart(call: Call) {
    logWithTime("responseBodyStart")
  }

  override fun responseBodyEnd(
    call: Call,
    byteCount: Long,
  ) {
    logWithTime("responseBodyEnd: byteCount=$byteCount")
  }

  override fun responseFailed(
    call: Call,
    ioe: IOException,
  ) {
    logWithTime("responseFailed: $ioe")
  }

  override fun callEnd(call: Call) {
    logWithTime("callEnd")
  }

  override fun callFailed(
    call: Call,
    ioe: IOException,
  ) {
    logWithTime("callFailed: $ioe")
  }

  override fun canceled(call: Call) {
    logWithTime("canceled")
  }

  override fun satisfactionFailure(
    call: Call,
    response: Response,
  ) {
    logWithTime("satisfactionFailure: $response")
  }

  override fun cacheHit(
    call: Call,
    response: Response,
  ) {
    logWithTime("cacheHit: $response")
  }

  override fun cacheMiss(call: Call) {
    logWithTime("cacheMiss")
  }

  override fun cacheConditionalHit(
    call: Call,
    cachedResponse: Response,
  ) {
    logWithTime("cacheConditionalHit: $cachedResponse")
  }

  private fun logWithTime(message: String) {
    val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
    logger.log("[$timeMs ms] $message")
  }

  open class Factory
    @JvmOverloads
    constructor(
      private val logger: HttpLoggingInterceptor.Logger = HttpLoggingInterceptor.Logger.DEFAULT,
    ) : EventListener.Factory {
      override fun create(call: Call): EventListener = LoggingEventListener(logger)
    }

  companion object
}
